package org.mirrentools.remoteattendance;

import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import org.mirrentools.remoteattendance.common.Requests;
import org.mirrentools.remoteattendance.common.StringUtil;
import org.mirrentools.remoteattendance.models.WorkRecordCVF;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 应用主控制器
 *
 * @author <a href="https://mirrentools.org">Mirren</a>
 */
public class MainController implements Initializable {
  /**
   * 最后一次异常的提醒时间
   */
  private long lastErrorTipsTime = 0;
  /**
   * 截屏的计数调度器的
   */
  private CounterSchedule screenshotSchedule;
  /**
   * 工作时长的字符串模板
   */
  private final String WORK_COUNT_TEMPLATE = "%.2f小时 刷新";
  /**
   * 考勤日历中的Pane合集
   */
  private final Map<String, StackPane> stackPaneMap = new LinkedHashMap<>();

  @FXML
  private Label lblNowTime;
  @FXML
  private Label lblWorkCount;
  @FXML
  private Button btnWork;
  @FXML
  private Button btnRest;
  @FXML
  private Button btnTodayReport;
  @FXML
  private Button btnHistoryReport;
  /**
   * 是否正在工作,true=是,false=不是
   */
  private boolean isWorked = true;

  /**
   * 今天的考勤状态
   */
  @FXML
  private TextArea txtTodayReport;
  @FXML
  private RadioButton radioTodayNormal;
  @FXML
  private RadioButton radioTodayLeave;
  @FXML
  private RadioButton radioTodayOvertime;
  @FXML
  private RadioButton radioTodayExchange;
  /**
   * 某一天的考勤状态
   */
  @FXML
  private TextArea txtHistoryReport;
  @FXML
  private RadioButton radioHistoryNormal;
  @FXML
  private RadioButton radioHistoryLeave;
  @FXML
  private RadioButton radioHistoryOvertime;
  @FXML
  private RadioButton radioHistoryExchange;
  /**
   * 考勤详情
   */
  @FXML
  private TableView<WorkRecordCVF> tbWorkRecord;
  /**
   * 月份选择
   */
  @FXML
  private ChoiceBox<String> choMouths;
  /**
   * 日期表格
   */
  @FXML
  private GridPane gridPanel;
  /**
   * 历史考勤的视图
   */
  @FXML
  private AnchorPane panelHistoryAttendView;
  /**
   * 历史考勤正在加载中
   */
  @FXML
  private StackPane panelHistoryAttendLoading;

  /**
   * 最后考勤的id
   */
  private Long lastWorkId;
  /**
   * 已截图数量
   */
  private final AtomicInteger screenshotCount = new AtomicInteger(0);
  /**
   * 截图上传失败数量
   */
  private final AtomicInteger screenshotFailedCount = new AtomicInteger(0);
  /**
   * 最后考勤的开始时间
   */
  private Long lastWorkStartTime;
  /**
   * 今天的考勤状态,0=全勤,1=请假,2=加班,3=调班补班
   */
  private int dataTodayAttendState = 0;
  /**
   * 最后选中的考勤日子
   */
  private String lastHistorySelectPaneId;
  /**
   * 历史的考勤状态,0=全勤,1=请假,2=加班,3=调班补班
   */
  private int dataHistoryAttendState = 0;
  /**
   * 历史的考勤状态选中的年
   */
  private int dataHistoryAttendYear = 0;
  /**
   * 历史的考勤状态选中的月
   */
  private int dataHistoryAttendMonth = 0;
  /**
   * 历史的考勤状态选中的年日子
   */
  private int dataHistoryAttendDay = 0;

  @Override
  public void initialize(URL url, ResourceBundle resourceBundle) {
    try {
      initLabelTimeNow();
      initLastWorkState();
      initTableWorkRecord();
      setWorkBtnState();
      initDatePanel(new ArrayList<>());
      initChoiceMouths();
      int interval = MainApplication.OPTIONS.interval();
      screenshotSchedule = new CounterSchedule();
      screenshotSchedule.setExecutor(Executors.newFixedThreadPool(1));
      screenshotSchedule.setPeriod(Duration.seconds(1));
      screenshotSchedule.setOnScheduled(e -> {
        long count = screenshotSchedule.getCount();
        if (count % interval == 0) {
          startScreenshot();
        }
      });
      screenshotSchedule.setOnCancelled(e -> screenshotSchedule.setCount(0));
    } catch (Throwable e) {
      String tips = "执行初始化应用-->失败!";
      Logger.log(tips, e);
      showAlert(tips, 2);
      e.printStackTrace();
    }
  }

  /**
   * 初始化最后的考勤记录并设置考勤状态
   */
  private void initLastWorkState() {
    Future<JsonObject> future = Requests.get(Requests.PATH_GET_TODAY_LAST_WORK);
    future.onSuccess(res -> {
      try {
        JsonObject data = res.getJsonObject("data");
        if (data != null) {
          Platform.runLater(() -> {
            Long id = data.getLong("id");
            Long end = data.getLong("end");
            Long start = data.getLong("start");
            if (id != null && end == null) {
              isWorked = true;
              lastWorkId = id;
              lastWorkStartTime = start;
              if (!screenshotSchedule.isRunning()) {
                screenshotSchedule.restart();
              }
            } else {
              isWorked = false;
            }
            setWorkBtnState();
          });
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    });
    future.onFailure(err -> {
      String tips = "执行初始化工作状态-->失败!";
      Logger.log(tips, err);
      showAlert(tips, 2);
    });
  }

  /**
   * 开始工作
   */
  public void onWork() {
    isWorked = true;
    setWorkBtnState();
    Future<JsonObject> future = Requests.get(Requests.PATH_POST_WORK);
    future.onSuccess(res -> Platform.runLater(() -> {
      initLastWorkState();
      getWorkRecord(0, 0, 0);
      if (!screenshotSchedule.isRunning()) {
        screenshotSchedule.restart();
      }
    }));
    future.onFailure(err -> {
      String tips = "执行提交开始工作-->失败!";
      Logger.log(tips, err);
      showAlert(tips, 2);
      isWorked = false;
      setWorkBtnState();
    });
    this.panelHistoryAttendLoading.setVisible(false);
    this.panelHistoryAttendView.setVisible(false);
  }

  /**
   * 工作休息
   */
  public void onRest() {
    isWorked = false;
    setWorkBtnState();
    Future<JsonObject> future = Requests.get(Requests.PATH_POST_REST, MultiMap.caseInsensitiveMultiMap().add("id", Long.toString(lastWorkId)));
    future.onSuccess(res -> Platform.runLater(() -> {
      initLastWorkState();
      getWorkRecord(0, 0, 0);
      this.screenshotSchedule.cancel();
    }));
    future.onFailure(err -> {
      String tips = "执行提交工作休息-->失败!";
      Logger.log(tips, err);
      showAlert(tips, 2);
      isWorked = true;
      setWorkBtnState();
    });
    this.panelHistoryAttendLoading.setVisible(false);
    this.panelHistoryAttendView.setVisible(false);
  }

  /**
   * 设置是否工作中按钮的状态
   */
  private void setWorkBtnState() {
    Platform.runLater(() -> {
      if (!isWorked) {
        btnWork.setDisable(false);
        btnRest.setDisable(true);
      } else {
        btnWork.setDisable(true);
        btnRest.setDisable(false);
      }
    });
  }

  /**
   * 提交今天的报告
   */
  public void onTodayReportSubmit() {
    this.btnTodayReport.setDisable(true);
    String body = txtTodayReport.getText();
    if (StringUtil.isNullOrEmpty(body)) {
      showAlert("汇报内容不能为空", 0);
      return;
    }
    int state = this.dataTodayAttendState;

    MultiMap multiMap = MultiMap.caseInsensitiveMultiMap();
    multiMap.add("body", body)
            .add("state", Integer.toString(state));
    Future<JsonObject> future = Requests.post(Requests.PATH_PUT_REPORT, multiMap);
    future.onSuccess(res -> {
      Logger.log("onTodayReportSubmit" + res);
      Platform.runLater(() -> {
        showAlert("提交成功!", 0);
        this.btnTodayReport.setDisable(false);
        initLastWorkState();
        getWorkRecord(0, 0, 0);
        onMouthsSelect();
      });
    });
    future.onFailure(err -> {
      Platform.runLater(() -> this.btnTodayReport.setDisable(false));
      String tips = "执行提交工作报告-->失败!";
      Logger.log(tips, err);
      showAlert(tips, 2);
    });
  }

  /**
   * 提交历史报告
   */
  public void onHistoryReportSubmit() {
    String body = txtHistoryReport.getText();
    if (StringUtil.isNullOrEmpty(body)) {
      showAlert("汇报内容不能为空", 0);
      return;
    }
    LocalDate now = LocalDate.now();
    int year = this.dataHistoryAttendYear;
    int month = this.dataHistoryAttendMonth;
    int day = this.dataHistoryAttendDay;
    LocalDate of = LocalDate.of(year, month, day);
    if (of.toEpochDay() > now.toEpochDay()) {
      showAlert("日期不能大于今天", 1);
      return;
    }
    this.btnHistoryReport.setDisable(true);
    int state = this.dataHistoryAttendState;
    MultiMap multiMap = MultiMap.caseInsensitiveMultiMap();
    multiMap.add("year", Integer.toString(year))
            .add("month", Integer.toString(month))
            .add("day", Integer.toString(day)).add("body", body)
            .add("state", Integer.toString(state));
    Future<JsonObject> future = Requests.post(Requests.PATH_PUT_REPORT, multiMap);
    future.onSuccess(res -> {
      Logger.log("onHistoryReportSubmit" + res);
      Platform.runLater(() -> {
        showAlert("提交成功!", 0);
        this.btnHistoryReport.setDisable(false);
        onMouthsSelect();
      });
    });
    future.onFailure(err -> {
      Platform.runLater(() -> this.btnHistoryReport.setDisable(false));
      String tips = "执行提交历史工作汇报-->失败!";
      Logger.log(tips, err);
      showAlert(tips, 2);
    });
  }

  /**
   * 刷新考勤记录和时长
   */
  public void onReloadWorkCount() {
    this.panelHistoryAttendLoading.setVisible(false);
    this.panelHistoryAttendView.setVisible(false);
    getWorkRecord(0, 0, 0);
    if (lastHistorySelectPaneId != null) {
      setAttendPaneBorder("null");
      lastHistorySelectPaneId = null;
    }
  }

  /**
   * 初始化刷新当前时间
   */
  private void initLabelTimeNow() {
    StringProperty time = new SimpleStringProperty();
    Timeline clock = new Timeline();
    KeyFrame frame = new KeyFrame(Duration.seconds(1), e -> {
      String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
      if (lastWorkStartTime != null && lastWorkStartTime != 0) {
        long w = System.currentTimeMillis() - lastWorkStartTime;
        float fc = Objects.equals(0L, w) ? 0f : ((float) w / 3600000L);
        format += String.format(" 已工作%.2f小时", fc);

      }
      int sc = screenshotCount.get();
      if (sc > 0) {
        format += ",已截图:" + sc;
      }
      int fsc = screenshotFailedCount.get();
      if (fsc > 0) {
        format += ",图片上传失败数量:" + fsc;
      }
      time.set(format);
    });
    clock.getKeyFrames().add(frame);
    clock.setCycleCount(Timeline.INDEFINITE);
    clock.play();
    lblNowTime.textProperty().bind(time);
  }


  /**
   * 初始化工作数据的表格
   */
  private void initTableWorkRecord() {
    TableColumn<WorkRecordCVF, String> start = new TableColumn<>("工作时间");
    start.setCellValueFactory(new PropertyValueFactory<>("start"));
    TableColumn<WorkRecordCVF, String> end = new TableColumn<>("休息时间");
    end.setCellValueFactory(new PropertyValueFactory<>("end"));
    TableColumn<WorkRecordCVF, String> count = new TableColumn<>("工作时长");
    count.setCellValueFactory(new PropertyValueFactory<>("wtime"));
    tbWorkRecord.getColumns().add(start);
    tbWorkRecord.getColumns().add(end);
    tbWorkRecord.getColumns().add(count);
    tbWorkRecord.setPlaceholder(new Label("尚无工作记录"));
    tbWorkRecord.setColumnResizePolicy(resize -> {
      double width = resize.getTable().getWidth() - 20;
      start.setPrefWidth(width / 3);
      end.setPrefWidth(width / 3);
      count.setPrefWidth(width / 3);
      return true;
    });
    getWorkRecord(0, 0, 0);
    ObservableList<WorkRecordCVF> items = FXCollections.observableArrayList();
    tbWorkRecord.setItems(items);
  }

  /**
   * 获取某一天的考勤数据
   *
   * @param year  年份
   * @param mouth 月份
   * @param day   日子 如果为0则不添加年月日
   */
  private void getWorkRecord(int year, int mouth, int day) {
    HttpRequest<Buffer> request = MainApplication.WEB_CLIENT.getAbs(Requests.PATH_FIND_DAYS_RECORD);
    if (day != 0) {
      this.panelHistoryAttendView.setVisible(false);
      this.panelHistoryAttendLoading.setVisible(true);
      request.addQueryParam("year", Integer.toString(year));
      request.addQueryParam("month", Integer.toString(mouth));
      request.addQueryParam("day", Integer.toString(day));
    }
    lblWorkCount.setText("正在加载中...");
    tbWorkRecord.getItems().clear();
    Future<JsonObject> future = Requests.request(request);
    future.onSuccess(res -> {
      try {
        JsonObject data = res.getJsonObject("data");
        Logger.log("getWorkRecord:" + data);
        if (data == null || data.isEmpty()) {
          return;
        }
        int state = StringUtil.getInteger(data.getInteger("state"), -1);
        String report = StringUtil.get(data.getString("report"), "");
        Long time = StringUtil.getLong(data.getLong("wtime"), 0L);
        float finalCount = Objects.equals(0L, time) ? 0 : ((float) time / 3600000L);
        Platform.runLater(() -> {
          lblWorkCount.setText(String.format(WORK_COUNT_TEMPLATE, finalCount));
          if (day == 0) {
            txtTodayReport.setText(report);
            setSelectState(state, 0);
          } else {
            panelHistoryAttendLoading.setVisible(false);
            panelHistoryAttendView.setVisible(true);
            txtHistoryReport.setText(report);
            setSelectState(state, 1);
          }
        });
        JsonArray items = data.getJsonArray("items");
        if (items != null && !items.isEmpty()) {
          Platform.runLater(() -> {
            for (int i = 0; i < items.size(); i++) {
              try {
                JsonObject d = items.getJsonObject(i);
                WorkRecordCVF cvf = new WorkRecordCVF();
                cvf.setStart(StringUtil.get(d.getString("start"), ""));
                cvf.setEnd(StringUtil.get(d.getString("end"), ""));
                Long w = StringUtil.getLong(d.getLong("wtime"), 0L);
                if (Objects.equals(0L, w)) {
                  cvf.setWtime("");
                } else {
                  float fc = Objects.equals(0L, w) ? 0f : ((float) w / 3600000L);
                  cvf.setWtime(String.format("%.2f", fc));
                }
                tbWorkRecord.getItems().add(cvf);
              } catch (Exception e) {
                String tips = "执行加载考勤记录-->失败!";
                Logger.log(tips, e);
                showAlert(tips, 2);
              }
            }
          });
        }
      } catch (Exception e) {
        String tips = "执行加载考勤记录-->失败!";
        Logger.log(tips, e);
        showAlert(tips, 2);
      }
    });
    future.onFailure(err -> {
      String tips = "执行获取考勤记录-->失败!";
      Logger.log(tips, err);
      showAlert(tips, 2);
    });

  }


  /**
   * 初始化月份选择
   */
  private void initChoiceMouths() {
    Future<JsonObject> future = Requests.get(Requests.PATH_FIND_CALENDAR_GROUP);
    future.onSuccess(res -> {
      Logger.log("initChoiceMouths:" + res);
      JsonArray data = res.getJsonArray("data");
      if (data != null && !data.isEmpty()) {
        Platform.runLater(() -> {
          ObservableList<String> list = FXCollections.observableArrayList();
          for (int i = 0; i < data.size(); i++) {
            JsonObject d = data.getJsonObject(i);
            if (d != null) {
              String m = d.getString("m");
              if (m != null && m.contains("-")) {
                list.add(m);
              }
            }
          }
          choMouths.setItems(list);
          choMouths.setOnAction(e -> onMouthsSelect());
          LocalDate now = LocalDate.now();
          String date = String.format("%s-%d", now.getYear(), now.getMonthValue());
          choMouths.getSelectionModel().select(date);
        });
      }
    }).onFailure(err -> {
      String tips = "执行初始化考勤的月份选择-->失败!";
      Logger.log(tips, err);
      showAlert(tips, 2);
    });
  }

  /**
   * 选择本月
   */
  public void choThisMouth() {
    LocalDate now = LocalDate.now();
    String date = String.format("%s-%d", now.getYear(), now.getMonthValue());
    choMouths.getSelectionModel().select(date);
  }

  /**
   * 选择上一个月
   */
  public void choPrevMouth() {
    LocalDate now = LocalDate.now();
    int year = now.getYear();
    int month = now.getMonthValue();
    String item = choMouths.getSelectionModel().getSelectedItem();
    if (item != null && item.contains("-")) {
      try {
        String[] split = item.split("-");
        year = Integer.parseInt(split[0].trim());
        month = Integer.parseInt(split[1].trim()) - 1;
      } catch (Exception e) {
        String tips = "执行加载选择上一个月的行事历-->失败!";
        Logger.log(tips, e);
        showAlert(tips, 2);
      }
    }
    if (month < 1) {
      year -= 1;
      month = 12;
    }
    String date = String.format("%s-%d", year, month);
    choMouths.getSelectionModel().select(date);
  }

  /**
   * 选择下一个月
   */
  public void choNextMouth() {
    LocalDate now = LocalDate.now();
    int year = now.getYear();
    int month = now.getMonthValue();
    String item = choMouths.getSelectionModel().getSelectedItem();
    if (item != null && item.contains("-")) {
      try {
        String[] split = item.split("-");
        year = Integer.parseInt(split[0].trim());
        month = Integer.parseInt(split[1].trim()) + 1;
      } catch (Exception e) {
        String tips = "执行加载选择下一个月的行事历-->失败!";
        Logger.log(tips, e);
        showAlert(tips, 2);
      }
    }
    if (month > 12) {
      year += 1;
      month = 1;
    }
    String date = String.format("%s-%d", year, month);
    choMouths.getSelectionModel().select(date);
  }

  /**
   * 月份选中事件
   */
  public void onMouthsSelect() {
    SingleSelectionModel<String> model = choMouths.getSelectionModel();
    String s = model.getSelectedItem();
    if (!s.contains("-")) {
      return;
    }
    String[] split = s.split("-");
    LocalDate now = LocalDate.now();
    int year = StringUtil.getInteger(split[0].trim(), now.getYear());
    int month = StringUtil.getInteger(split[1].trim(), now.getMonthValue());
    MultiMap query = MultiMap.caseInsensitiveMultiMap();
    query.add("year", Integer.toString(year));
    query.add("month", Integer.toString(month));
    Future<JsonObject> future = Requests.get(Requests.PATH_FIND_CALENDAR, query);
    future.onSuccess(res -> {
      Logger.log("onMouthsSelect:" + res);
      JsonArray data = res.getJsonArray("data");
      List<JsonObject> items = new ArrayList<>();
      for (int i = 0; i < data.size(); i++) {
        items.add(data.getJsonObject(i));
      }
      initDatePanel(items);
    });
    future.onFailure(err -> {
      String tips = "执行选择考勤:" + year + "-" + month + "-->失败!";
      Logger.log(tips, err);
      showAlert(tips, 2);
    });
  }

  /**
   * 初始化行事历以及用户的考勤统计
   *
   * @param items ys(int):年份,ms(int):月份,ds(int):日子,w(int):是否需要上班,u(int):用户是否上班
   */
  private void initDatePanel(List<JsonObject> items) {
    Platform.runLater(() -> {
      gridPanel.getChildren().clear();
      stackPaneMap.clear();
      initDatePanelTitle();
      if (items.isEmpty()) {
        StackPane pane = new StackPane();
        pane.getChildren().add(new Label("尚无数据"));
        gridPanel.add(pane, 0, 1);
        return;
      }
      int index = 0;
      int maxIndex = items.size();
      int startDay = getStartDay(items.get(0));
      for (int row = 1; row < 7; row++) {
        for (int col = 0; col < 7; col++) {
          if (index >= maxIndex) {
            break;
          }
          String paneId = row + "-" + col;
          StackPane pane = new StackPane();
          pane.setId(paneId);
          if (startDay > 0) {
            startDay--;
            gridPanel.add(pane, col, row);
            continue;
          }
          try {
            JsonObject data = items.get(index++);
            pane.setUserData(data);
            Integer days = StringUtil.getInteger(data.getInteger("ds"), index);
            Integer w = StringUtil.getInteger(data.getInteger("w"), 0);
            Integer u = StringUtil.getInteger(data.getInteger("u"), 0);
            pane.setBackground(new Background(new BackgroundFill(Color.WHITE, null, null)));
            Label day = new Label(Integer.toString(days));
            StackPane.setAlignment(day, Pos.TOP_CENTER);
            day.setAlignment(Pos.CENTER);
            day.setPadding(new Insets(8, 10, 8, 10));
            Label sys = new Label("");
            if (Objects.equals(1, w)) {
              sys.setBackground(new Background(new BackgroundFill(Color.rgb(0, 220, 0), null, null)));
            }
            StackPane.setAlignment(sys, Pos.BOTTOM_LEFT);
            sys.setPadding(new Insets(4, 10, 4, 10));
            Label usr = new Label("");
            if (Objects.equals(0, u)) {
              usr.setBackground(new Background(new BackgroundFill(Color.rgb(0, 220, 0), null, null)));
            } else if (Objects.equals(1, u)) {
              usr.setBackground(new Background(new BackgroundFill(Color.rgb(245, 108, 108), null, null)));
            } else if (Objects.equals(2, u)) {
              usr.setBackground(new Background(new BackgroundFill(Color.rgb(64, 158, 255), null, null)));
            } else if (Objects.equals(3, u)) {
              usr.setBackground(new Background(new BackgroundFill(Color.rgb(230, 162, 60), null, null)));
            }
            usr.setPadding(new Insets(4, 10, 4, 10));
            StackPane.setAlignment(usr, Pos.BOTTOM_RIGHT);
            pane.getChildren().addAll(day, sys, usr);
            pane.widthProperty().addListener(r -> {
              double width = pane.getWidth();
              day.setPrefWidth(width);
              sys.setPrefWidth(width / 2);
              usr.setPrefWidth(width / 2);
            });
            pane.setOnMouseClicked(e -> selectAttendHandler(paneId, data));
            stackPaneMap.put(paneId, pane);
            gridPanel.add(pane, col, row);
            setAttendPaneBorder(lastHistorySelectPaneId);
          } catch (Exception e) {
            String tips = "执行初始化行事历-->失败!";
            Logger.log(tips, e);
            showAlert(tips, 2);
          }
        }
      }
    });
  }

  /**
   * 考勤日历选中指定内容的事件处理器
   *
   * @param data 考勤日历数据
   */
  private void selectAttendHandler(String paneId, JsonObject data) {
    setAttendPaneBorder(paneId);
    Integer ys = data.getInteger("ys");
    Integer ms = data.getInteger("ms");
    Integer ds = data.getInteger("ds");
    lastHistorySelectPaneId = paneId;
    dataHistoryAttendYear = ys;
    dataHistoryAttendMonth = ms;
    dataHistoryAttendDay = ds;
    getWorkRecord(ys, ms, ds);
  }

  /**
   * 给选中的行事历加上边框
   *
   * @param paneId pane的id
   */
  private void setAttendPaneBorder(String paneId) {
    if (paneId == null) {
      return;
    }
    stackPaneMap.forEach((k, v) -> {
      if (Objects.equals(k, paneId)) {
        v.setStyle("-fx-border-color:red");
      } else {
        v.setStyle("-fx-border-color:none");
      }
    });
  }

  /**
   * 获取日期在星期几
   *
   * @param data ys=年,ms=月,ds=天
   */
  public int getStartDay(JsonObject data) {
    Integer ys = data.getInteger("ys");
    Integer ms = data.getInteger("ms");
    Integer ds = data.getInteger("ds");
    LocalDate localDate = LocalDate.of(ys, ms, ds);
    return localDate.getDayOfWeek().getValue();
  }

  /**
   * 初始化行事历与考勤的日历标题
   */
  private void initDatePanelTitle() {
    for (int i = 0; i < 7; i++) {
      StackPane pane = new StackPane();
      pane.setAlignment(Pos.CENTER);
      pane.setBackground(new Background(new BackgroundFill(Color.WHITE, null, null)));
      Label title = new Label();
      switch (i) {
        case 0 -> {
          title.setText("日");
          title.setTextFill(Color.RED);
        }
        case 1 -> title.setText("一");
        case 2 -> title.setText("二");
        case 3 -> title.setText("三");
        case 4 -> title.setText("四");
        case 5 -> title.setText("五");
        case 6 -> title.setText("六");
      }
      pane.getChildren().add(title);
      gridPanel.add(pane, i, 0);
    }
  }

  /**
   * 今天的考勤状态
   */
  public void onSelectTodayState() {
    if (radioTodayLeave.isSelected()) {
      dataTodayAttendState = 1;
    } else if (radioTodayOvertime.isSelected()) {
      dataTodayAttendState = 2;
    } else if (radioTodayExchange.isSelected()) {
      dataTodayAttendState = 3;
    } else {
      dataTodayAttendState = 0;
    }
  }

  /**
   * 历史的考勤状态
   */
  public void onSelectHistoryState() {
    if (radioHistoryLeave.isSelected()) {
      dataHistoryAttendState = 1;
    } else if (radioHistoryOvertime.isSelected()) {
      dataHistoryAttendState = 2;
    } else if (radioHistoryExchange.isSelected()) {
      dataHistoryAttendState = 3;
    } else {
      dataHistoryAttendState = 0;
    }
  }

  /**
   * 更新考勤状态
   *
   * @param state 状态:0=全勤,1=请假,2=加班,3=调班补班
   * @param who   0=今天,1=历史
   */
  private void setSelectState(int state, int who) {
    if (who == 1) {
      if (state == 1) {
        radioHistoryLeave.setSelected(true);
      } else if (state == 2) {
        radioHistoryOvertime.setSelected(true);
      } else if (state == 3) {
        radioHistoryExchange.setSelected(true);
      } else {
        radioHistoryNormal.setSelected(true);
        if (state != 0) {
          radioHistoryNormal.setSelected(false);
        }
      }
      dataHistoryAttendState = state;
    } else {
      if (state == 1) {
        radioTodayLeave.setSelected(true);
      } else if (state == 2) {
        radioTodayOvertime.setSelected(true);
      } else if (state == 3) {
        radioTodayExchange.setSelected(true);
      } else {
        state = 0;
        radioTodayNormal.setSelected(true);
      }
      dataTodayAttendState = state;
    }
  }

  /**
   * 显示消息弹窗
   *
   * @param msg  信息
   * @param type 弹窗的类型,0=普通提醒,1=警告提醒,2=异常
   */
  private void showAlert(String msg, int type) {
    MainApplication.showAlert(msg, type);
  }


  /**
   * 开始截图前操作
   */
  private void startScreenshot() {
    Logger.log("开始截图...");
    try {
      GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
      GraphicsDevice[] devices = env.getScreenDevices();
      int width = 0;
      int height = 0;
      for (GraphicsDevice d : devices) {
        width += d.getDisplayMode().getWidth();
        int ht = d.getDisplayMode().getHeight();
        if (ht > height) {
          height = ht;
        }
      }
      capterImg(width, height);
    } catch (Exception e) {
      e.printStackTrace();
      try {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        int width = (int) (screenSize.getWidth());
        int height = (int) screenSize.getHeight();
        capterImg(width, height);
      } catch (Exception e1) {
        String tips = "执行屏幕截图->获取屏幕尺寸-->失败!";
        Logger.log(tips, e1);
        showAlert(tips, 2);
      }
    }
  }

  /**
   * 截图图片
   *
   * @param width  图片的宽度
   * @param height 图片的高度
   */
  private void capterImg(int width, int height) {
    try {
      // 利用awt中的方法，通过记录的起始点和长宽完成屏幕截图
      Robot robot = new Robot();
      Dimension dimension = new Dimension(width, height);
      Rectangle re = new Rectangle(dimension);
      BufferedImage screenCapture = robot.createScreenCapture(re);
      /* 调整后的图片的宽度和高度 */
      float resizeTimes = MainApplication.OPTIONS.pixel();
      int toWidth = (int) (width * resizeTimes);
      int toHeight = (int) (height * resizeTimes);
      BufferedImage image = new BufferedImage(toWidth, toHeight, BufferedImage.TYPE_INT_RGB);
      image.getGraphics().drawImage(screenCapture.getScaledInstance(toWidth, toHeight, Image.SCALE_SMOOTH), 0, 0, null);
      // 将截取图片放入到系统固定位置
      try (ByteArrayOutputStream byteOS = new ByteArrayOutputStream()) {
        ImageIO.write(image, "png", byteOS);
        byteOS.flush();
        byte[] bytes = byteOS.toByteArray();
        Buffer buffer = Buffer.buffer(bytes);
        screenshotCount.incrementAndGet();
        Future<JsonObject> future = Requests.uploadFile(Requests.PATH_UPLOAD_RECORD_IMAGE, buffer);
        future.onSuccess(res -> Logger.log("上传图片成功!"));
        future.onFailure(err -> {
          screenshotFailedCount.incrementAndGet();
          saveImage(image);
          long lt = System.currentTimeMillis() - lastErrorTipsTime;
          if (lt > (1000 * 60 * 20)) {
            lastErrorTipsTime = System.currentTimeMillis();
            String tips = "执行上传屏幕截图-->失败!";
            Logger.log(tips, err);
            showAlert(tips, 2);
          }
        });
      }
    } catch (Exception e) {
      String tips = "截图屏幕图片-->失败!";
      Logger.log(tips, e);
      showAlert(tips, 2);
    }
  }

  /**
   * 保存图片到本地
   *
   * @param image 图片信息
   */
  private void saveImage(BufferedImage image) {
    try {
      LocalDateTime now = LocalDateTime.now();
      String imageName = now.format(DateTimeFormatter.ofPattern("HHmmss"));
      String folder = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
      String root = MainApplication.USER_DIR + "/screenshot/" + folder + "/";
      if (Files.notExists(Paths.get(root))) {
        Files.createDirectories(Paths.get(root));
      }
      File file = new File(root + imageName + ".png");
      ImageIO.write(image, "png", file);

    } catch (Exception e) {
      String tips = "截图屏幕图片-->失败!";
      Logger.log(tips, e);
      showAlert(tips, 2);
    }
  }

}